package validator

import (
	"context"
	"errors"
	"fmt"
	"gitee.com/zackeus/go-boot/common/constants/codes"
	"gitee.com/zackeus/go-boot/common/constants/net/headers"
	"gitee.com/zackeus/go-boot/tools/errorx"
	constants "gitee.com/zackeus/go-boot/validator/tags"
	enTranslations "gitee.com/zackeus/go-boot/validator/translations/en"
	zhTranslations "gitee.com/zackeus/go-boot/validator/translations/zh"
	"gitee.com/zackeus/goutil/arrutil"
	"github.com/go-playground/locales"
	"github.com/go-playground/locales/en"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	"golang.org/x/text/language"
	"net/http"
	"strings"
)

const defaultLocal = "en"

type Validate struct {
	engine     *validator.Validate
	translator *ut.UniversalTranslator
}

// wrapFunc wraps normal Func makes it compatible with FuncCtx
func wrapFunc(fn validator.Func) validator.FuncCtx {
	if fn == nil {
		return nil // be sure not to wrap a bad function.
	}
	return func(ctx context.Context, fl validator.FieldLevel) bool {
		return fn(fl)
	}
}

// 注册翻译
func (v *Validate) initTranslations(local string) error {
	/* 设置翻译语言 */
	trans, found := v.translator.GetTranslator(local)
	if !found {
		return errors.New(fmt.Sprintf("The Translator of %s can't find.", local))
	}

	switch local {
	case "zh":
		/* 中文 */
		return zhTranslations.RegisterDefaultTranslations(v.engine, trans)
	case "en":
		/* 英文 */
		return enTranslations.RegisterDefaultTranslations(v.engine, trans)
	default:
		return enTranslations.RegisterDefaultTranslations(v.engine, trans)
	}
}

// 注册校验
func (v *Validate) initValidations() error {
	if err := v.engine.RegisterValidation(constants.TagFieldLen, validationFieldLength, false); err != nil {
		return err
	}
	if err := v.engine.RegisterValidation(constants.TagChoiceSingleDigit, validationChoiceSingleDigit, false); err != nil {
		return err
	}
	if err := v.engine.RegisterValidation(constants.TagDtmf, validationDTMF, false); err != nil {
		return err
	}
	return nil
}

func New(supports ...locales.Translator) (*Validate, error) {
	/* 默认翻译器 */
	fallback := en.New()

	locals := []string{fallback.Locale()}
	for _, support := range supports {
		locals = append(locals, support.Locale())
	}
	/* 去重 */
	locals = arrutil.Unique(locals)

	/* 校验器 */
	engine := validator.New()
	/* 翻译器 */
	translator := ut.New(fallback, supports...)

	v := &Validate{engine: engine, translator: translator}
	for _, local := range locals {
		/* 注册翻译 */
		if err := v.initTranslations(local); err != nil {
			return nil, err
		}
	}
	/* 注册校验 */
	if err := v.initValidations(); err != nil {
		return nil, err
	}
	return v, nil
}

// Struct 校验
func (v *Validate) Struct(s any) error {
	return v.StructCtx(context.Background(), s)
}

func (v *Validate) StructReq(r *http.Request, s any) error {
	/* 解析 Accept-Language */
	tags, _, err := language.ParseAcceptLanguage(r.Header.Get(headers.AcceptLanguage))
	if err != nil {
		return err
	}

	locals := make([]string, 0)
	for _, tag := range tags {
		locals = append(locals, tag.String())
	}

	return v.StructCtx(r.Context(), s, locals...)
}

// StructCtx 校验
// s: 待校验结构体
// locals: 翻译器
func (v *Validate) StructCtx(ctx context.Context, s any, locals ...string) error {
	err := v.engine.StructCtx(ctx, s)
	if err == nil {
		return nil
	}

	/* 参数校验错误 */
	if errs, ok := err.(validator.ValidationErrors); ok {
		locals = append(locals, defaultLocal)
		/* 获取翻译器 */
		trans, _ := v.translator.FindTranslator(locals...)

		var sliceErrs []string
		for _, e := range errs {
			sliceErrs = append(sliceErrs, e.Translate(trans))
		}
		return errorx.Fail(codes.UnprocessableEntity, strings.Join(sliceErrs, ", "))
	}
	return err
}

func (v *Validate) Validate(r *http.Request, data any) error {
	return v.StructReq(r, data)
}

// RegisterValidation 自定义校验
func (v *Validate) RegisterValidation(tag string, fn validator.Func, callValidationEvenIfNull ...bool) error {
	return v.RegisterValidationCtx(tag, wrapFunc(fn), callValidationEvenIfNull...)
}

// RegisterValidationCtx 自定义校验
func (v *Validate) RegisterValidationCtx(tag string, fn validator.FuncCtx, callValidationEvenIfNull ...bool) error {
	return v.engine.RegisterValidationCtx(tag, fn, callValidationEvenIfNull...)
}
